package com.gitee.wsl.mathematics.matrix

import com.gitee.wsl.mathematics.number.DoubleCalcContainer
import com.gitee.wsl.mathematics.number.FloatCalcContainer
import com.gitee.wsl.mathematics.vector.MutableVector
import com.gitee.wsl.mathematics.vector.Vector
import com.gitee.wsl.mathematics.vector.VectorCalcContainer
import kotlin.math.abs

/** A matrix element value initializer by row and col indexes. */
internal typealias MatInitializer<T> = (Int, Int) -> T
typealias MatCopyInitializer<T> = T.(Int, Int) -> T

interface Matrix<T:Number,V:Matrix<T,V,R>,R:Vector<T,R>>: VectorCalcContainer<T, V>{

    val rowCount: Int
    val colCount: Int

    val rowIndices get() = 0 until rowCount
    val colIndices get() = 0 until colCount

    val m:List<Vector<T,R>>
    val n:List<Vector<T,R>>

    val lastRow get() = M[ rowCount-1 ]

    val lastColumn get() = N[ colCount-1 ]

    /**
     * Get the dimension of this square, for non-square matrix it will return -1
     * 获取方阵的维数，非方阵的维数定义为 -1
     */
    val dim get() = if (rowCount == colCount) rowCount else -1

    val rank:T get() = zero
    val trace:T get() = zero

    val det:T

    val adjoin: V

    val inverse: V
        get() = adjoin.let {
            val det = it.det
            if (det == zero) {
                throw IllegalArgumentException("Matrix determinant is zero:\n$this")
            }
            it / det
            //create(a00 / det, a01 / det, a10 / det, a11 / det)
        }


    val transpose :V get() = create{ i, j -> this[j][i] }

    val isSquare get()  = rowCount == colCount
    val isNotSquare get()  = !isSquare

    val isTridiagonal: Boolean get(){
        return forEachCheck { i, j -> (abs(i - j) >= 2) && (!abs(this[i][j]).isApproxZero())}
        /*for (i in 0 until rowCount) {
            for (j in 0 until colCount) {
                if (abs(i - j) >= 2) {
                    if (!abs(this[i][j]).isApproxZero()) return false
                }
            }
        }
        return true*/
    }


    val rowIdentity:V get() = create{ i, j -> if(i == j) one else zero }

    val isOrthogonal: Boolean get() {
        if (rowCount != colCount) return false
        return (this * transpose - rowIdentity).F().isApproxZero()
    }

    val isSymmetrical: Boolean get() {
        if (rowCount != colCount) return false
        return (this - transpose).F().isApproxZero()
    }

    val isUpperTriangular: Boolean get() {
        return forEachCheck { row, col -> !this[row][col].isApproxZero() }
        /*for (i in 0 until rowCount) {
            for (j in 0 until i) {
                if (!this[i][j].isApproxZero()) return false
            }
        }
        return true*/
    }

    private fun F(): T {
        var result = zero
        forEachItem { _, _, value ->
            result += value*value
        }
        return sqrt(result)
    }

    fun toMutable():MutableMatrix<T,V,R>

    fun createVec(indexes:List<Int>):Vector<T,R>

    fun create(initializer: MatInitializer<T>):V{
        val newList = mutableListOf<T>()
        /*for (i in 0 until rowCount) {
            for (j in 0 until colCount) {
                newList.add(initializer(i,j))
            }
        }*/
        forEachIndex { row, col -> newList.add(initializer(row,col)) }
        return create(newList)
    }

    operator fun times(other: V) :V

    operator fun div(other: V) :V = inverse * other

    /*val abs:V
        get() = create(proxy.map{ i ->  abs(i)  })
    val sign:V
        get() = create(proxy.map{ i ->  sign(i)  })
    val ceil:V
        get() = create(proxy.map{ i ->  ceil(i)  })
    val floor:V
        get() = create(proxy.map{ i ->  floor(i)  })
    val round:V
        get() = create(proxy.map{ i ->  round(i)  })

    val valueArray: List<T> get() =  proxy.values()*/

    //fun getValue(rowIndex:Int,colIndex:Int):T = proxy[rowIndex col colIndex]

    //fun createVec(indexRage:IntRange) = createVec(indexRage.toList())
    /*fun create(valueArray: List<T>):V

    operator fun plus(other: V) :V = create(proxy.mapIndexed{ index, i ->  i + other.proxy[index] })

    operator fun minus(other: V) :V = create(proxy.mapIndexed{ index, i ->  i - other.proxy[index] })

    operator fun times(a: Number) :V = create( proxy.map{ i ->  i * a })

    operator fun div(a: Number):V = create( proxy.map{ i ->  i / a })

    fun divBy(a: Number) :V = create( proxy.map{ i ->  i.divBy(a) })

    operator fun rem(a: Number):V = create( proxy.map{ i ->  i.rem(a) })
    //operator fun rem(other: V) :V //= create((0..proxy.size).mapIndexed{ index, i ->  i ^ other.proxy[index] })
    fun remBy(a: Number) :V =create( proxy.map{  i ->  i.remBy(a) })

    operator fun unaryMinus() : V = times(-1)*/

    operator fun get(row: Int) = createVec(rowIndexes(row))

    fun getColumn(colIndex: Int) = N[colIndex]

    operator fun get(row: Int, col: Int) = proxy[row col col]

    infix fun Int.col(colIndex: Int) = colCount * this + colIndex

    operator fun Vector<T,*>.get(index:Int) = this.proxy[index]

    operator fun MutableVector<T, *>.set(index:Int, value: T){
        this.proxy[index] = value
    }

    operator fun M.get(row: Int, col: Int) = row to col

    operator fun N.get(col: Int, row: Int) = row to col

    operator fun M.get(row: Int) = createVec(rowIndexes(row))

    operator fun N.get(col: Int) = createVec(colIndexes(col))

    //fun rowIndexes(row:Int) = (colCount * row..< colCount*(row+1)).toList()

    //fun colIndexes(colIndex:Int) = (0..<rowCount).map { it col colIndex }

    fun rowIndexes(row:Int) = proxy.getIndexes(row*colCount,colCount)

    fun colIndexes(col:Int) = proxy.getIndexes(start = col, count = rowCount, step = colCount)

    fun dump() {
        /*
        for (r in 0 until  rowCount) {
            for (c in 0  until colCount) {
                print("${this[r, c]} ")
            }
            println()
        }
        */
        forEachItemWithState { _, _, value, endCol ->
            print("$value ")
            if(endCol) println()
        }
    }

    fun forEachCheck(breakFun:(row: Int, col: Int)->Boolean):Boolean{
        for (r in 0 until  rowCount) {
            for (c in 0  until colCount) {
                if(breakFun(r,c)) return false
            }
        }
        return true
    }

    fun forEachIndex(block:(row: Int, col: Int)->Unit){
        for (r in 0 until  rowCount) {
            for (c in 0  until colCount) {
                block(r,c)
            }
        }
    }

    fun forEachItem(block:(row: Int, col: Int,value: T)->Unit){
        forEachIndex { r, c -> block(r,c,this[r,c]) }
        /*
        for (r in 0 until  rowCount) {
            for (c in 0  until colCount) {
                block(r,c,this[r,c])
            }
        }
        */
    }

    fun forEachItem(startRow:Int,endRow:Int=rowCount-1,startCol:Int,endCol:Int=colCount-1,block:(row: Int, col: Int,value: T)->Unit){
        require(endRow<rowCount && startRow in 0..endRow)
        require(endCol<colCount && startCol in 0..endCol)

        for (r in startRow ..  endRow) {
            for (c in startCol  ..endCol) {
                block(r,c,this[r,c])
            }
        }
    }

    fun forEachItemWithState(block:(row: Int, col: Int,value: T,endCol:Boolean)->Unit){
        for (r in 0 until  rowCount) {
            for (c in 0  until colCount) {
                block(r,c,this[r,c],c == colCount-1)
            }
        }
    }

    //operator fun A.get(row: Int, col: Int):Matrix<T,*,*>

    fun visit(visitor:MatrixVisitor<T>){
        visitor.start(this)
        forEachItem(visitor::visit)
        visitor.end()
    }
}

object M
object N
object A

interface MatrixD<V:Matrix<Double,V,R>,R:Vector<Double,R>>:Matrix<Double,V,R>,DoubleCalcContainer

interface MatrixF<V:Matrix<Float,V,R>,R:Vector<Float,R>>:Matrix<Float,V,R>, FloatCalcContainer

/*private inline fun mapIndex(rowCounts: Int,colCounts:Int,rowIndex: Int,colIndex:Int):Int{
    return colCounts * rowIndex + colIndex
}*/

/*inline fun <T:Number,reified V:Matrix<T,V>> V._createRows(matrix: V) = buildList(matrix.rowCount){
    indices.forEach {i->
        this[i] = createVec(matrix.colCount * i..< matrix.colCount*(i+1))
   }
}

inline fun <T:Number,reified V:Matrix<T,V>> _createCols(matrix: V) = buildList(matrix.colCount){
    indices.forEach {i->
        val values = ArrayList<Int>(matrix.rowCount)
        values.indices.forEach { j->
            values[j]= matrix.rowCount * j + i
        }
        this[i] = matrix.createVec(values)
    }
}*/


/*
fun <T:Number,V:Matrix<T,V>> _createCols(matrix: Matrix<T,V>) = ArrayList(matrix.colCount){i->
    val values = ArrayList<Int>(matrix.rowCount)
    (0..matrix.rowCount).forEach { j->
        values[j]= matrix.rowCount*j + i
    }
    matrix.createVec(values)
}
*/
